﻿//-----------------------------------------------------------------------
// <Copyright>
// * Copyright (C) 2022 RuYiAdmin All Rights Reserved
// </Copyright>
//-----------------------------------------------------------------------

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RuYiAdmin.Net.CommonInfrastructure.Classes;
using RuYiAdmin.Net.CommonInfrastructure.Constants.System;
using RuYiAdmin.Net.CommonInfrastructure.Enums.Business;
using RuYiAdmin.Net.CommonInfrastructure.Exceptions.Framework;
using RuYiAdmin.Net.CommonInfrastructure.Utilities.Utils;
using RuYiAdmin.Net.EntityDataModel.DataTransformationModel.SystemModel;
using RuYiAdmin.Net.ServiceLayer.BusinessService.Interface.FrameworkInterface;
using RuYiAdmin.Net.ServiceLayer.BusinessServiceExtension;
using RuYiAdmin.Net.WebApi.RuYiAdminCode.RuYiAdminClass;
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace RuYiAdmin.Net.WebApi.RuYiAdminCode.RuYiAdminExtension
{
    /// <summary>
    /// 全局异常处理中间件
    /// </summary>
    public class GlobalExceptionHandlerExtensions
    {
        #region 属性及其构造函数

        private readonly RequestDelegate Next;  //上下文请求 
        private readonly ILogger<GlobalExceptionHandlerExtensions> ConsoleLogger;
        private readonly IMQService MQService;
        private readonly ILoggerService LoggerService;
        private readonly IRedisService RedisService;


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="Next">上下文请求</param>
        /// <param name="ConsoleLogger">日志对象</param>
        /// <param name="MQService">消息队列实例</param>
        /// <param name="LoggerService">业务日志实例</param>
        /// <param name="RedisService">高速缓存实例</param>
        public GlobalExceptionHandlerExtensions(RequestDelegate Next,
                                                ILogger<GlobalExceptionHandlerExtensions> ConsoleLogger,
                                                IMQService MQService,
                                                ILoggerService LoggerService,
                                                IRedisService RedisService)
        {
            this.Next = Next;
            this.ConsoleLogger = ConsoleLogger;
            this.MQService = MQService;
            this.LoggerService = LoggerService;
            this.RedisService = RedisService;
        }

        #endregion

        #region 处理上下文请求

        /// <summary>
        /// 处理上下文请求
        /// </summary>
        /// <param name="httpContext">http会话对象</param>
        /// <returns></returns>
        public async Task InvokeAsync(HttpContext httpContext)
        {
            try
            {
                await this.Next(httpContext); //处理上下文请求
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(httpContext, ex); //捕获异常,在HandleExceptionAsync中处理
            }
        }

        #endregion

        #region 全局统一异常处理

        /// <summary>
        /// 全局统一异常处理
        /// </summary>
        /// <param name="context">http会话对象</param>
        /// <param name="exception">全局异常处理</param>
        /// <returns></returns>
        private async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            context.Response.ContentType = "application/json";  //返回json 类型
            var response = context.Response;

            var errorResponse = new ErrorResponse { Success = false };

            // 自定义的异常错误信息类型
            switch (exception)
            {
                case ApplicationException ex:
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    errorResponse.Message = ex.Message;
                    break;
                case RuYiAdminCustomException ex:
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    errorResponse.Message = ex.Message;

                    #region 记录SQL注入式攻击行为

                    if (ex.Message.Contains(WarnningMessage.SqlInjectionAttackMessage))
                    {
                        #region 记录SQL注入式攻击审计日志

                        var token = context.GetToken();
                        //获取用户
                        var user = this.RedisService.Get<SysUserDTO>(token);

                        var log = SysLogServiceExtension.GetSysLog(context);

                        //字段赋值
                        log.OperationType = OperationType.SqlInjectionAttack;
                        log.Remark = $"{string.Join("/", user.LogonName, user.DisplayName)}对系统进行了Sql注入式攻击，使用口令为{token}，请管理员警惕、关注！";

                        await log.WriteAsync();

                        #endregion

                        #region 发送Sql注入式攻击告警消息

                        var msg = new SystemMessage();

                        msg.Message = "Broadcast";
                        msg.MessageType = MessageType.Broadcast;

                        BroadcastMessage broadcastMessage = new BroadcastMessage();
                        broadcastMessage.Title = "Sql注入式攻击告警";
                        broadcastMessage.Message = log.Remark;
                        broadcastMessage.MessageLevel = MessageLevel.Warning;

                        msg.Object = broadcastMessage;

                        this.MQService.SendTopic(JsonConvert.SerializeObject(msg));

                        #endregion

                        #region 强制下线记录审计日志

                        log = SysLogServiceExtension.GetSysLog(context);

                        //字段赋值
                        log.OperationType = OperationType.ForceLogout;
                        log.Remark = $"{user.DisplayName + "/" + user.LogonName}由于SQL注入式攻击被系统强制下线";

                        await log.WriteAsync();

                        #endregion

                        //token作废
                        this.RedisService.Delete(new string[] { token });

                        #region 强制用户下线

                        msg = new SystemMessage();
                        msg.Message = "ForceLogout";
                        msg.MessageType = MessageType.ForceLogout;
                        msg.Object = user;

                        this.MQService.SendTopic(JsonConvert.SerializeObject(msg));

                        #endregion
                    }

                    #endregion

                    break;
                default:
                    response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    errorResponse.Message = "Internal Server Error";
                    break;
            }

            //错误日志控制台输出
            this.ConsoleLogger.LogError(exception.Message);

            //错误日志文件输出
            var errorInfo = new StringBuilder();
            //errorInfo.AppendLine("Time:" + DateTime.Now);
            errorInfo.AppendLine("StackTrace:" + (exception.StackTrace == null ? "" : exception.StackTrace));
            errorInfo.AppendLine("Source:" + (exception.Source == null ? "" : exception.Source));
            errorInfo.AppendLine("Message:" + exception.Message);
            errorInfo.AppendLine("InnerExceptionMessage:" + (exception.InnerException == null ? "" : exception.InnerException.Message));
            this.LoggerService.Error(errorInfo.ToString());

            //返回统一异常
            var result = errorResponse;
            if (!context.Response.HasStarted)
            {
                await context.Response.WriteAsync(result.ToJson());
            }
        }

        #endregion
    }
}
